package top.codedance.iotp.tools.terminal.ui;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.common.util.SystemUtil;
import top.codedance.iotp.tools.terminal.TerminalApplication;
import top.codedance.iotp.tools.terminal.bean.ComConnectParam;
import top.codedance.iotp.tools.terminal.comer.CallOwner;
import top.codedance.iotp.tools.terminal.comer.ComClient;

import javax.swing.*;
import javax.xml.bind.DatatypeConverter;
import java.awt.event.ItemEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author xuedianfu
 */
public class DownloadFile extends javax.swing.JDialog implements CallOwner {
    private ShellFrame sf;
    private static ExecutorService pool = Executors.newCachedThreadPool();
    private static volatile Map<String, Long> tasksOverLength = new HashMap<>();
    private static volatile Map<String, Map<Integer, File>> tasksTempFile = new HashMap<>();
    private static volatile Map<String, Thread> tasksJoiner = new HashMap<>();
    private JSONObject currentConnectionParam;
    private JSONObject connectData = new JSONObject();
    private DefaultComboBoxModel<String> dlmB;
    private static short RUNNER_TYPE = 3;
    private static int i = 0;
    private static int taskId = i;

    /**
     * Creates new form DownloadFile
     */
    public DownloadFile(ShellFrame sf) {
        super(sf, true);
        this.sf = sf;
        initComponents();
        URL url = Thread.currentThread().getContextClassLoader().getResource("logo.gif");
        ImageIcon icon = new ImageIcon(url);
        setIconImage(icon.getImage());
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setLocationRelativeTo(this.sf);
        setTitle("下载文件");

        jpb_status.setStringPainted(true);
        jpb_status.setBorderPainted(true);

        initList();
    }

    private synchronized void initData(String taskId) {
        if (!tasksOverLength.containsKey(taskId)) {
            tasksOverLength.put(taskId, 0l);
            tasksTempFile.put(taskId, new HashMap<>());
        }
    }

    private synchronized Long getTasksOverLength(String taskId) {
        return tasksOverLength.get(taskId);
    }

    private synchronized void accumulation(String taskId, int length) {
        tasksOverLength.put(taskId, tasksOverLength.get(taskId) + length);
    }

    private synchronized void addTempFile(String taskId, Integer dataIndex, File file) {
        Map<Integer, File> integerFileMap = tasksTempFile.get(taskId);
        integerFileMap.put(dataIndex, file);
        tasksTempFile.put(taskId, integerFileMap);
    }

    private synchronized boolean existsTempFile(String taskId, Integer dataIndex) {
        if (tasksTempFile.containsKey(taskId)) {
            return tasksTempFile.get(taskId).containsKey(dataIndex);
        } else {
            return false;
        }
    }

    private synchronized void addJoinner(String cTaskId, Thread t) {
        tasksJoiner.put(cTaskId, t);
    }

    private synchronized void removeJoinner(String cTaskId) {
        tasksJoiner.remove(cTaskId);
    }

    private synchronized boolean existsJoinner(String cTaskId) {
        return tasksJoiner.containsKey(cTaskId);
    }

    private int getTaskId() {
        taskId = i++;
        return taskId;
    }

    public void initList() {
        dlmB = new DefaultComboBoxModel<>();
        this.jcb_connections.setModel(dlmB);
        initListData();
    }

    public void initListData() {
        String s = SystemUtil.readTempFile(ConnectionManager.CONNECTION_TMP_FILENAME);
        if (null != s && !s.isEmpty()) {
            connectData = JSON.parseObject(s);
            dlmB.removeAllElements();
            Set<Map.Entry<String, Object>> entries = connectData.entrySet();
            JSONObject jo;
            JSONObject comJo;
            for (Map.Entry<String, Object> entry : entries) {
                jo = (JSONObject) entry.getValue();
                if (jo.containsKey("type") && jo.getString("type").equals("com") && jo.containsKey("manage") && jo.getBoolean("manage")) {
                    dlmB.addElement(entry.getKey());
                }
            }
            this.jcb_connections.repaint();
            String key = (String) jcb_connections.getSelectedItem();
            currentConnectionParam = this.connectData.getJSONObject(key);
        }
    }

    private void comSendCmd(String cmd) {
        if (currentConnectionParam == null || currentConnectionParam.isEmpty()) {
            JOptionPane.showMessageDialog(rootPane, "请选取连接配置。");
            return;
        }
        if (!currentConnectionParam.containsKey("com") || currentConnectionParam.getString("com").isEmpty()) {
            JOptionPane.showMessageDialog(rootPane, "请选取Com口的连接配置。");
            return;
        }
        JSONObject com = currentConnectionParam.getJSONObject("com");
        String comName = com.getString("value");
        String btl = currentConnectionParam.getString("btl");
        String sjw = currentConnectionParam.getString("sjw");
        String tzw = currentConnectionParam.getString("tzw");
        String xyw = currentConnectionParam.getString("xyw");
        if (!sf.ccs.containsKey(comName)) {
            createComClient(comName, btl, sjw, tzw, xyw);
        } else {
            ComClient comClient = sf.ccs.get(comName);
            comClient.setOwner(this);
        }
        ComClient comClient = sf.ccs.get(comName);
        comClient.send(cmd);
    }

    private void createComClient(String comName, String btl, String sjw, String tzw, String xyw) {
        sf.ccs.put(comName, new ComClient(this, new ComConnectParam(comName, Integer.parseInt(btl), Integer.parseInt(sjw), Integer.parseInt(tzw), Integer.parseInt(xyw))));
    }

    @Override
    public String getOwnerClassName() {
        return this.getClass().getTypeName();
    }

    @Override
    public void appendResult(final String data) {
        pool.submit(() -> {
            try {
                JSONObject jsonObject = JSONObject.parseObject(data);
                final String cTaskId = jsonObject.getString("taskId");
                int opration = jsonObject.getIntValue("opration");
                String strDir = System.getProperty("user.home");
                String runtimeDirPath = strDir + File.separatorChar + ".iot/";
                String tempDirPath = runtimeDirPath + "download/" + cTaskId + "/";
                final File tempDir = new File(tempDirPath);
                if (opration == 0) {
                    final Integer dataIndex = jsonObject.getInteger("dataIndex");
                    final Long contentLength = jsonObject.getLong("contentLength");
                    int readLength = jsonObject.getIntValue("bodyLength");
                    byte[] body = DatatypeConverter.parseHexBinary(jsonObject.getString("body"));
                    String target = jtf_target.getText();
                    String filename = jsonObject.getString("filename");
                    final String endClientFilePath = target + File.separatorChar + filename;
                    if (!tempDir.exists()) {
                        tempDir.mkdirs();
                    }
                    synchronized (this.getClass()) {
                        if (!tasksOverLength.containsKey(cTaskId)) {
                            initData(cTaskId);
                        }
                    }

                    if (!existsTempFile(cTaskId, dataIndex)) {
                        String downloadTempFragmentPath = tempDirPath + dataIndex;
                        File tmpWriteFile = new File(downloadTempFragmentPath);
                        tmpWriteFile.createNewFile();
                        FileOutputStream fos = new FileOutputStream(tmpWriteFile);
                        fos.write(body);
                        fos.flush();
                        fos.close();
                        accumulation(cTaskId, readLength);
                        addTempFile(cTaskId, dataIndex, tmpWriteFile);
                        Double percentage = Math.floor(getTasksOverLength(cTaskId) * 1.0 / contentLength * 100);
                        jpb_status.setValue(percentage.intValue());
                        TerminalApplication.syslogInfo("Download complete : " + percentage + "%");
                    }

                    synchronized (this.getClass()) {
                        if (!existsJoinner(cTaskId)) {
                            Thread t = new Thread(() -> {
                                while (true) {
                                    if (getTasksOverLength(cTaskId) >= contentLength) {
                                        try {
                                            TerminalApplication.syslogInfo("Start join download file to: " + endClientFilePath);
                                            Map<Integer, File> taskTempMap = tasksTempFile.get(cTaskId);
                                            File file = new File(endClientFilePath);
                                            if (file.exists()) {
                                                if(jcb_replace.isSelected()) {
                                                    file.delete();
                                                }else{
                                                    file.renameTo(new File(file.getAbsolutePath() + ".bak"));
                                                }
                                            }
                                            FileChannel resultFileChannel = new FileOutputStream(file, true).getChannel();
                                            for (int i = 0; i < taskTempMap.size(); i++) {
                                                File file2 = taskTempMap.get(i);
                                                FileChannel blk = new FileInputStream(file2).getChannel();
                                                resultFileChannel.transferFrom(blk, resultFileChannel.size(), blk.size());
                                                blk.close();
                                                TerminalApplication.syslogInfo("Join complete : " + Math.round(i * 1.0 / taskTempMap.size() * 100) + "%");
                                            }
                                            resultFileChannel.close();
                                            tasksOverLength.remove(cTaskId);
                                            tasksTempFile.remove(cTaskId);
                                            for (int i = 0; i < taskTempMap.size(); i++) {
                                                File file2 = taskTempMap.get(i);
                                                if (file2.exists()) {
                                                    file2.delete();
                                                }
                                            }
                                            File parentFile = tempDir.getParentFile();
                                            tempDir.delete();
                                            parentFile.delete();
                                            removeJoinner(cTaskId);
                                            TerminalApplication.syslogInfo("Download complete.");
                                            jb_download.setEnabled(true);
                                            jb_cancel.setEnabled(false);
                                            JOptionPane.showMessageDialog(this.getRootPane(), "下载成功");
                                            break;
                                        } catch (Exception e) {
                                            TerminalApplication.syslogInfo("Download fail.");
                                        }
                                    } else {
                                        try {
                                            Thread.sleep(1000);
                                        } catch (Exception e) {
                                        }
                                    }
                                }
                            });
                            synchronized (this.getClass()) {
                                if (!existsJoinner(cTaskId)) {
                                    addJoinner(cTaskId, t);
                                    t.start();
                                }
                            }
                        }
                    }
                    JSONObject command = new JSONObject();
                    JSONObject cmd = new JSONObject();
                    cmd.put("runner", RUNNER_TYPE);
                    cmd.put("opration", 2);
                    cmd.put("taskId", cTaskId);
                    cmd.put("ok", true);
                    command.put("cmd", cmd);
                    comSendCmd(command.toJSONString());
                }
                if (opration == 1) {
                    Map<Integer, File> taskTempMap = tasksTempFile.get(cTaskId);
                    for (int i = 0; i < taskTempMap.size(); i++) {
                        File file2 = taskTempMap.get(i);
                        if (file2.exists()) {
                            file2.delete();
                        }
                    }
                    tempDir.delete();
                    tasksTempFile.remove(cTaskId);
                    jpb_status.setValue(0);
                    jb_download.setEnabled(true);
                    jb_cancel.setEnabled(false);
                    JOptionPane.showMessageDialog(this.getRootPane(), "已取消");
                }
            } catch (Exception e) {
                TerminalApplication.syslogInfo("Download file fail: " + e.getMessage());
            }
        });
    }

    @Override
    public void appendError(String data) {
        if (data != null) {
            JSONObject jsonObject = JSON.parseObject(data);
            JOptionPane.showMessageDialog(this.getRootPane(), "下载失败：" + jsonObject.getString("msg"));
            jb_download.setEnabled(true);
            jb_cancel.setEnabled(false);
            jpb_status.setValue(0);
        }
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jtf_target = new javax.swing.JTextField();
        jb_brower = new javax.swing.JButton();
        jLabel3 = new javax.swing.JLabel();
        jtf_source = new javax.swing.JTextField();
        jLabel4 = new javax.swing.JLabel();
        jpb_status = new javax.swing.JProgressBar();
        jb_cancel = new javax.swing.JButton();
        jb_download = new javax.swing.JButton();
        jLabel5 = new javax.swing.JLabel();
        jcb_connections = new javax.swing.JComboBox<>();
        jLabel6 = new javax.swing.JLabel();
        jcb_replace = new javax.swing.JCheckBox();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);

        jLabel1.setText("下载远程文件");

        jLabel2.setText("下载到：");

        jtf_target.setEditable(false);

        jb_brower.setText("浏览...");
        jb_brower.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jb_browerActionPerformed(evt);
            }
        });

        jLabel3.setText("下载地址：");

        jtf_source.setToolTipText("要下载的文件地址");

        jLabel4.setText("下载进度：");

        jb_cancel.setText("取消");
        jb_cancel.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jb_cancelActionPerformed(evt);
            }
        });

        jb_download.setText("下载");
        jb_download.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jb_downloadActionPerformed(evt);
            }
        });

        jLabel5.setText("连接：");

        jcb_connections.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                jcb_connectionsItemStateChanged(evt);
            }
        });

        jLabel6.setText("冲突：");

        jcb_replace.setText("覆盖同位置同名文件");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(229, 229, 229)
                        .addComponent(jLabel1))
                    .addGroup(layout.createSequentialGroup()
                        .addContainerGap()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                            .addGroup(layout.createSequentialGroup()
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                            .addComponent(jLabel2)
                                            .addComponent(jLabel3))
                                        .addGap(18, 18, 18)
                                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                                            .addComponent(jtf_target)
                                            .addComponent(jtf_source, javax.swing.GroupLayout.DEFAULT_SIZE, 356, Short.MAX_VALUE)))
                                    .addGroup(layout.createSequentialGroup()
                                        .addComponent(jLabel5)
                                        .addGap(48, 48, 48)
                                        .addComponent(jcb_connections, javax.swing.GroupLayout.PREFERRED_SIZE, 356, javax.swing.GroupLayout.PREFERRED_SIZE))
                                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                                        .addComponent(jLabel4)
                                        .addGap(18, 18, 18)
                                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                            .addComponent(jcb_replace)
                                            .addComponent(jpb_status, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(jb_brower))
                            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                .addComponent(jLabel6)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(jb_download)
                                .addGap(18, 18, 18)
                                .addComponent(jb_cancel)))))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel1)
                .addGap(11, 11, 11)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel5)
                    .addComponent(jcb_connections, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel2)
                    .addComponent(jtf_target, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jb_brower))
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel3)
                    .addComponent(jtf_source, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(jpb_status, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jb_cancel)
                    .addComponent(jb_download)
                    .addComponent(jLabel6)
                    .addComponent(jcb_replace))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void jb_cancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jb_cancelActionPerformed
        int i = JOptionPane.showConfirmDialog(rootPane, "确定要取消吗。");
        if (JOptionPane.YES_OPTION == i) {
            JSONObject command = new JSONObject();
            JSONObject cmd = new JSONObject();
            cmd.put("runner", RUNNER_TYPE);
            cmd.put("opration", 1);
            cmd.put("taskId", taskId);
            command.put("cmd", cmd);
            comSendCmd(command.toJSONString());
        }
    }//GEN-LAST:event_jb_cancelActionPerformed

    private void jb_downloadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jb_downloadActionPerformed
        if (jtf_target.getText().isEmpty()) {
            JOptionPane.showMessageDialog(this.getRootPane(), "请选取下载的文件夹");
            return;
        }
        if (jtf_source.getText().isEmpty()) {
            JOptionPane.showMessageDialog(this.getRootPane(), "请输入要下载的文件全路径");
            return;
        }
        int i = JOptionPane.showConfirmDialog(rootPane, "确定要下载吗。");
        if (JOptionPane.YES_OPTION == i) {
            jpb_status.setValue(0);
            JSONObject command = new JSONObject();
            JSONObject cmd = new JSONObject();
            cmd.put("runner", RUNNER_TYPE);
            cmd.put("opration", 0);
            cmd.put("taskId", getTaskId());
            cmd.put("source", jtf_source.getText());
            command.put("cmd", cmd);
            comSendCmd(command.toJSONString());
            jb_download.setEnabled(false);
            jb_cancel.setEnabled(true);
        }
    }//GEN-LAST:event_jb_downloadActionPerformed

    private void jb_browerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jb_browerActionPerformed
        JFileChooser jfc = new JFileChooser();
        jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        int i = jfc.showOpenDialog(this);
        if (i == JFileChooser.APPROVE_OPTION) {
            File selectedFile = jfc.getSelectedFile();
            if (selectedFile.exists() && selectedFile.isDirectory()) {
                jtf_target.setText(selectedFile.getAbsolutePath());
            } else {
                JOptionPane.showMessageDialog(this.getRootPane(), "请选取一个目录");
            }
        }
    }//GEN-LAST:event_jb_browerActionPerformed

    private void jcb_connectionsItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_jcb_connectionsItemStateChanged
        if (evt.getStateChange() == ItemEvent.SELECTED) {
            String key = (String) jcb_connections.getSelectedItem();
            currentConnectionParam = this.connectData.getJSONObject(key);
        }
    }//GEN-LAST:event_jcb_connectionsItemStateChanged

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JLabel jLabel6;
    private javax.swing.JButton jb_brower;
    private javax.swing.JButton jb_cancel;
    private javax.swing.JButton jb_download;
    private javax.swing.JComboBox<String> jcb_connections;
    private javax.swing.JCheckBox jcb_replace;
    private javax.swing.JProgressBar jpb_status;
    private javax.swing.JTextField jtf_source;
    private javax.swing.JTextField jtf_target;
    // End of variables declaration//GEN-END:variables
}
